package com.xiyuan.smartutils.client.ws;

import com.xiyuan.smartutils.client.ws.core.DefaultWebSocketPacketResolver;
import com.xiyuan.smartutils.client.ws.core.WebSocketPacketResolver;
import com.xiyuan.smartutils.client.ws.core.WebsocketPacketHeader;
import com.xiyuan.smartutils.client.ws.core.impl.rfc6455.DefaultWebsocketPacket;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Random;

/**
 * WebSocket数据包格式：<br>
 *
 * <a href="https://tools.ietf.org/html/rfc6455#section-5.2">RFC 6455</a>
 * <pre>
 *  0                 1                   2                       3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-------+-+-------------+-------------------------------+
 * |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 * |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 * |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 * | |1|2|3|       |K|             |                               |
 * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 * |     Extended payload length continued, if payload len == 127  |
 * + - - - - - - - - - - - - - - - +-------------------------------+
 * |                               |Masking-key, if MASK set to 1  |
 * +-------------------------------+-------------------------------+
 * | Masking-key (continued)       |          Payload Data         |
 * +-------------------------------- - - - - - - - - - - - - - - - +
 * :                     Payload Data continued ...                :
 * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 * |                     Payload Data continued ...                |
 * +---------------------------------------------------------------+
 * <br>
 * 第一字节
 *   第1位：FIN，标识是否为此消息的最后一个数据包
 *   第2,3,4位保留
 *   第5,6,7,8位，opcode，操作码（0－15）：
 *     0x00：标识一个中间数据包
 *     0x01：标识一个text类型数据包
 *     0x02：标识一个binary类型数据包
 *     0x08：标识一个断开连接类型数据包
 *     0x09：标识一个ping类型数据包
 *     0x0A：表示一个pong类型数据包
 *     0x03-07：保留
 *     0x0B-0F：保留
 * <br>
 * 第二字节，
 *   第1位：MASK，标识是否有掩码
 *   第2-8位，标识有效数据长度（0-127）
 *     1、len < 126时，不需要扩展长度字段，len即数据长度
 *     2、len = 126时，扩展2字节表示数据长度
 *     3、len = 127时，扩展8字节表示数据长度
 * <br>
 * 0–999        保留段, 未使用.<br>
 * 1000         CLOSE_NORMAL            正常关闭; 无论为何目的而创建, 该链接都已成功完成任务.
 * 1001         CLOSE_GOING_AWAY        终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
 * 1002         CLOSE_PROTOCOL_ERROR    由于协议错误而中断连接.
 * 1003         CLOSE_UNSUPPORTED       由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据).
 * 1004                                 保留. 其意义可能会在未来定义.
 * 1005         CLOSE_NO_STATUS         保留.  表示没有收到预期的状态码.
 * 1006         CLOSE_ABNORMAL          保留. 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧).
 * 1007         Unsupported Data        由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据).
 * 1008         Policy Violation        由于收到不符合约定的数据而断开连接. 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景.
 * 1009         CLOSE_TOO_LARGE         由于收到过大的数据帧而断开连接.
 * 1010         Missing Extension       客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接.
 * 1011         Internal Error          客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接.
 * 1012         Service Restart         服务器由于重启而断开连接. [Ref]
 * 1013         Try Again Later         服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接. [Ref]
 * 1014                                 由 WebSocket 标准保留以便未来使用.
 * 1015         TLS Handshake           保留. 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书).
 * 1016–1999                            由 WebSocket 标准保留以便未来使用.
 * 2000–2999                            由 WebSocket 拓展保留使用.
 * 3000–3999                            可以由库或框架使用. 不应由应用使用. 可以在 IANA 注册, 先到先得.
 * 4000–4999                            可以由应用使用.
 * </pre>
 *
 * @author xiyuan 2022-07-10 @version 1.0.0 新建或整理
 */
@SuppressWarnings({"AlibabaAvoidStartWithDollarAndUnderLineNaming", "CharsetObjectCanBeUsed"})
public abstract class WebSocketPacket implements WebSocketConstants {
    public static final Charset _UTF8_C = Charset.forName("UTF-8");
    /**
     * the mask-key random.
     */
    protected static Random random = new Random();
    
    protected WebsocketPacketHeader header;
    /**
     * The contents.
     */
    protected byte[] body;
    
    /**
     * Instantiates a new text frame.
     *
     * @param body 内容主体
     */
    public WebSocketPacket(String body) {
        this((body == null ? "" : body).getBytes(_UTF8_C), Opcode.TEXT);
    }
    
    /**
     * Instantiates a new text frame.
     *
     * @param bytes content
     */
    public WebSocketPacket(byte[] bytes) {
        this(bytes == null ? _EMPTY_ : bytes, Opcode.BINARY);
    }
    
    /**
     * Instantiates a new empty body frame for opcode.
     *
     * @param opcode the frame opcode
     */
    public WebSocketPacket(Opcode opcode) {
        this(null, opcode);
    }
    
    /**
     * Instantiates a new body frame for opcode.
     *
     * @param body   the custom body.
     * @param opcode the opcode.
     */
    public WebSocketPacket(byte[] body, Opcode opcode) {
        int payloadLength = body == null ? 0 : body.length;
        this.header = WebsocketPacketHeader.parseHeader(false,
                                                        opcode,
                                                        2,
                                                        payloadLength,
                                                        PayloadLengthType.valueOf(payloadLength));
        this.body = body;
    }
    
    protected WebSocketPacket(byte[] body, WebsocketPacketHeader header) {
        this.header = header;
        this.body = body;
    }
    
    public WebsocketPacketHeader getHeader() {
        return header;
    }
    
    public abstract ByteBuffer toByteBuffer();
    
    public final byte[] getContent() {
        return body == null ? new byte[0] : body;
    }
    
    
    public final String getText() {
        return getText(null);
    }
    
    public final String getText(Charset charset) {
        return charset == null ? new String(getContent()) : new String(getContent(), charset);
    }
    
    
    public WebSocketPacket mask() {
        header.setMask(true);
        return this;
    }
    
    public WebSocketPacket unmask() {
        header.setMask(false);
        return this;
    }
    
    public static WebSocketPacketResolver newResolver() {
        return new DefaultWebSocketPacketResolver();
    }
    
    public static WebSocketPacket createPingPacket() {
        return new DefaultWebsocketPacket(Opcode.PING);
    }
    
    public static WebSocketPacket createPongPacket() {
        return new DefaultWebsocketPacket(Opcode.PONG);
    }
    
    public static WebSocketPacket createClosePacket() {
        return new DefaultWebsocketPacket(Opcode.CLOSE);
    }
    
    public static WebSocketPacket createTextPacket(String content) {
        return new DefaultWebsocketPacket(content.getBytes(), Opcode.TEXT);
    }
    
    public static WebSocketPacket createBinPacket(byte[] content) {
        return new DefaultWebsocketPacket(content, Opcode.BINARY);
    }
    
    /**
     * 创建中间数据包 用于 分片发送
     *
     * @param content
     * @return
     */
    public static WebSocketPacket createContinuationPacket(byte[] content) {
        
        return new DefaultWebsocketPacket(content, Opcode.CONTINUATION);
    }
}
